package gatech.cs3251.project4;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class FriendTracker extends Service {
	String logname = "FriendTracker";
	String server = "10.0.2.2";
	int socket = 25412;
	
	private final IBinder mBinder  = new LocalBinder();
	private ContentResolver cr;
	private volatile PollingThread pt;
	
	public static boolean isRunning = false;
	
	public class LocalBinder extends Binder
	{
		FriendTracker getService()
		{
			return FriendTracker.this;
		}
	}
	
	@Override
	public void onCreate()
	{
		super.onCreate();
		Log.v("CHRIS", "In FriendTracker onCreate");
		isRunning = true;
		makeDB();
//		makeTestDB();
//		readTestDB();
//		updateTestDB();
		readDB();
		Toast.makeText(this, "Service Created", Toast.LENGTH_LONG).show();
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId)
	{
		Log.v("CHRIS", "In FriendTracker onStartCommand");
		Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
		isRunning = true;
		pt = new PollingThread("PollingThread");
		pt.startThread();
		return START_STICKY;
	}
	
    @Override
	public IBinder onBind(Intent intent)
	{
		Log.v("CHRIS", "In FriendTracker onBind");
		return mBinder;
	}
    
	@Override
	public void onDestroy() {
		super.onDestroy();
		Log.v("CHRIS", "In FriendTracker onDestroy");
		isRunning = false;
		if(pt != null)
		{
			pt.stopThread();
		}
		Toast.makeText(this, "Destroyed", Toast.LENGTH_LONG).show();
	}

	
    private class PollingThread implements Runnable{

		private volatile Thread runner;
		private volatile boolean go = true;
		
		public PollingThread()
		{}
		
		public PollingThread(String name)
		{
			runner = new Thread(new PollingThread(), name);
			Log.v("CHRIS", "Creating and running PollingThread");
			runner.start();
		}
		
			public synchronized void startThread(){
			if(runner == null){
				runner = new Thread(this);
				runner.start();
			}
		}
	
		public synchronized void stopThread(){
			if(runner != null){
				Thread moribund = runner;
				runner = null;
				moribund.interrupt();
			}
		}

	    public void run()
	    {
	    	while(go)
	    	{
	    		try
	    		{
	    			Thread.sleep(1000);
	    			Log.v("CHRIS", "Pinging real server");
	    			updateDB();
	    		}
	    		catch(Exception e)
	    		{
//	    			Log.e("Exception:", e.getMessage());
	    			go = false;
//	    			Log.v("CHRIS", "Interrupted");
	    		}
	    	}
	    	//Log.v("CHRIS", "Polling thread run ended");
	    }
	    
    }
    
    private void makeDB()
    {
    	Log.v("CHRIS", "Making real DB");
    	cr = getContentResolver();
    	int[] keys = {11,22,33};
    	Log.v("CHRIS", "Querying server");
    	LocationFormat[] lf = getFriendsLocation(keys);
    	Log.v("CHRIS", "Queried server");
    	ContentValues cv = new ContentValues();
    	for(int i = 0; i < keys.length; i++)
    	{
			if(cr.query(Data.CONTENT_URI, new String[]{Data.userIdCol}, "USERID=" + keys[i], null, null).moveToFirst() == false)
		    {
				Log.v("CHRIS", "Inserting new entry");
				cv.put(Data.userIdCol, keys[i]);
				cv.put(Data.latitudeCol, LocationFormat.iToS(lf[i].latitude));
				cv.put(Data.longitudeCol, LocationFormat.iToS(lf[i].longitude));
				cv.put(Data.altitudeCol, LocationFormat.iToS(lf[i].altitude));
				cr.insert(Data.CONTENT_URI, cv);
		    }
    	}
    	Log.v("CHRIS", "Made real DB");
    }
    
    private void updateDB()
    {
    	Log.v("CHRIS", "Updating real DB");
    	
    }
    
    private void makeTestDB()
    {
    	Log.v("CHRIS", "Making test DB");
    	cr = getContentResolver();
    	int[] keys = {11,22,33};
//    	Log.v("CHRIS", "Querying server");
//    	LocationFormat[] lf = getFriendsLocation(keys);
//    	Log.v("CHRIS", "Queried server");
    	ContentValues cv = new ContentValues();
		if(cr.query(Data.CONTENT_URI, new String[]{Data.userIdCol}, "USERID=11", null, null).moveToFirst() == false)
	    {
			Log.v("CHRIS", "Inserting new entry");
			cv.put(Data.userIdCol, 11);
			cv.put(Data.latitudeCol, 34.4);
			cv.put(Data.longitudeCol, 45.6);
			cv.put(Data.altitudeCol, 3.3);
			cr.insert(Data.CONTENT_URI, cv);
	    }
		cv = new ContentValues();
		if(cr.query(Data.CONTENT_URI, new String[]{Data.userIdCol}, "USERID=22", null, null).moveToFirst() == false)
	    {
			Log.v("CHRIS", "Inserting new entry");
			cv.put(Data.userIdCol, 22);
			cv.put(Data.latitudeCol, 78.3);
			cv.put(Data.longitudeCol, 82.2);
			cv.put(Data.altitudeCol, 5.3);
			cr.insert(Data.CONTENT_URI, cv);
	    }
		cv = new ContentValues();
		if(cr.query(Data.CONTENT_URI, new String[]{Data.userIdCol}, "USERID=33", null, null).moveToFirst() == false)
	    {
			Log.v("CHRIS", "Inserting new entry");
			cv.put(Data.userIdCol, 33);
			cv.put(Data.latitudeCol, 11.2);
			cv.put(Data.longitudeCol, 55.3);
			cv.put(Data.altitudeCol, 9.0);
			cr.insert(Data.CONTENT_URI, cv);
	    }
    	Log.v("CHRIS", "Made test DB");
    }
    
    private void readDB()
    {
    	Log.v("CHRIS", "Reading test DB");
    	Cursor c = cr.query(Data.CONTENT_URI, null, null, null, null);
    	if(c.moveToFirst())
    	{
    		do
    		{
    		Log.v("CHRIS", "ID: " + c.getInt(c.getColumnIndex(Data.userIdCol)));
    		Log.v("CHRIS", "Lat: " + c.getString(c.getColumnIndex(Data.latitudeCol)));
    		Log.v("CHRIS", "Lon: " + c.getString(c.getColumnIndex(Data.longitudeCol)));
    		Log.v("CHRIS", "Alt: " + c.getString(c.getColumnIndex(Data.altitudeCol)));
    		}while(c.moveToNext());
    	}
    	else
    	{
    		Log.v("CHRIS", "No more cursor");
    	}
    	Log.v("CHRIS", "Finished reading test DB");
    }
    
    private void updateTestDB()
    {
    	Log.v("CHRIS", "Updating test DB");
    	ContentValues cv = new ContentValues();
    	cv.put(Data.userIdCol, 22);
		cv.put(Data.latitudeCol, 11.21);
		cv.put(Data.longitudeCol, 18.55);
		cv.put(Data.altitudeCol, 9.0);
    	cr.update(Data.CONTENT_URI, cv, "USERID=22", null);
    }
    
    
    /**
     * Send current location to the Server
     * @param location Your current Location
     * @return Success of update
     */
    public boolean updateLocation(LocationFormat location)
    {
    	Header header = new Header(MessageType.UPDATE,location.userId,1);
    	String response = queryServer(header.networkPrint()+location.networkPrint());
    	header = Header.parseHeader(response);
    	
    	if(header == null || header.type != MessageType.UPDATE_ACK)
    		return false;
    	
    	return true;
    }
	
    /**
     * Returns the current locations of requested Friends
     * @param userId User ID's to locate
     * @return An array of the found locations
     */
    public LocationFormat [] getFriendsLocation(int [] userId) {
    	Log.v("CHRIS", "In getFriendsLocation");
    	Header header = new Header(MessageType.FRIENDS_REQ,0,0);
    	
    	String query = header.networkPrint();
    	for( int i:userId ) {
    		query += LocationFormat.iToS(i);
    	}
    	String response = queryServer(query);
    	Log.v("CHRIS", "Response: " + response);
    	
    	header = Header.parseHeader(response);
    	
    	if(header == null || header.type != MessageType.FRIENDS_RESP)
    		return null;
    	
    	return getLocations(response, header.length);
    }
    
    /**
     * Get the locations history of one found location
     * @param userId ID of the desired user
     * @return An array of his previous locations
     */
    public LocationFormat [] getFriendsHistory(int userId) {
    	Log.v("CHRIS", "In getFriendsHistory");
    	Header header = new Header(MessageType.HISTORY_REQ,0,0);
    	
    	String response = queryServer(header.networkPrint()+LocationFormat.iToS(userId));
    	Log.v("CHRIS", "Response: " + response);
    	
    	header = Header.parseHeader(response);
    	
    	if(header == null || header.type != MessageType.HISTORY_RESP)
    		return null;
    	
    	return getLocations(response, header.length);
    }
    
    /**
     * Query the server
     * @param data Formated request
     * @return Formated response
     */
    private String queryServer(String data) {
    	try {
    		Log.v("CHRIS", "In queryServer");
    		//create a socket to the server
    		Socket sock = new Socket(server, socket);
    		Log.v("CHRIS", "Socket created");
    		
    		//Configuration
    		InputStreamReader instream = new InputStreamReader(sock.getInputStream());
    		OutputStream outstream = sock.getOutputStream();
    			//we want this print writer to auto-flush
    		Log.v("CHRIS", "Streams made");
    		PrintWriter printout = new PrintWriter(outstream, true);
    		Log.v("CHRIS", "Printwriter made");
    		
    		//send data
    		printout.print(data);
    		printout.flush();
    		Log.v("CHRIS", "Printed");
    		
    		//wait for response
    		char[] buffer = new char[2048];
    		int pointer = 0;
    		Header h = null;
//    		while(pointer < Header.size && pointer < h.length*LocationFormat.size) {
//    			if(pointer >= Header.size) {
//    				h=Header.parseHeader(buffer.toString());
//    			}
//    			int num_in = instream.read(buffer,pointer,2048-pointer);
//    			pointer += num_in;
//    		}
    		instream.read(buffer, 0, Header.size);
    		h = Header.parseHeader(buffer.toString());
    		instream.read(buffer, Header.size, h.length*LocationFormat.size);
    		return buffer.toString();
    	} catch(IOException e) {
    		Log.e(logname, "Error creating socket: "+e.getMessage());
    		return "";
    	}
    }
    
    /**
     * Retrieves the Locations out of the server response
     * @param s The whole server response String, including header
     * @param size The number of locations to extracts
     * @return Array of extracted locations
     */
    private LocationFormat [] getLocations(String s, int size) {
    	LocationFormat [] array = new LocationFormat[size];
    	
    	for(int i = 0; i<array.length; i++)
    		array[i] = LocationFormat.parseLocation(
    				s.substring(Header.size + i*LocationFormat.size, Header.size + (1+i)*LocationFormat.size + 1)
    				);
    	
    	return array;
    }
}